fix: pass the DOM height convention explicitly, and retire ambient LayoutSource - #14657
fix: pass the DOM height convention explicitly, and retire ambient LayoutSource#14657DrJKL wants to merge 2 commits into
Conversation
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Plus Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Comment |
🎭 Playwright: ✅ 1811 passed, 0 failed · 3 flaky📊 Browser Reports
🎨 Storybook: ✅ Built — View Storybook📦 Bundle Size
⚡ Performance Report
Absolute values
Raw data{
"timestamp": "2026-08-13T18:58:25.477Z",
"gitSha": "b48c06093cc08e353eee5167f98170d489fa1c83",
"branch": "drjkl/layout-source-at-instantiation",
"measurements": [
{
"name": "canvas-idle",
"durationMs": 2062.7079999999864,
"styleRecalcs": 7,
"styleRecalcDurationMs": 5.727000000000001,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 573.8449999999999,
"heapDeltaBytes": -20854904,
"heapUsedBytes": 47923204,
"domNodes": -283,
"jsHeapTotalBytes": 18845696,
"scriptDurationMs": 26.262999999999998,
"eventListeners": -157,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "canvas-idle",
"durationMs": 2054.0090000000646,
"styleRecalcs": 7,
"styleRecalcDurationMs": 7.0619999999999985,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 505.6860000000001,
"heapDeltaBytes": -21344788,
"heapUsedBytes": 47212928,
"domNodes": -282,
"jsHeapTotalBytes": 19107840,
"scriptDurationMs": 26.061999999999994,
"eventListeners": -157,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-mouse-sweep",
"durationMs": 2109.730000000013,
"styleRecalcs": 78,
"styleRecalcDurationMs": 38.212999999999994,
"layouts": 12,
"layoutDurationMs": 3.0429999999999997,
"taskDurationMs": 1182.315,
"heapDeltaBytes": -1018428,
"heapUsedBytes": 67939604,
"domNodes": -283,
"jsHeapTotalBytes": 21073920,
"scriptDurationMs": 166.125,
"eventListeners": -155,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-mouse-sweep",
"durationMs": 2199.6010000000297,
"styleRecalcs": 80,
"styleRecalcDurationMs": 40.003,
"layouts": 12,
"layoutDurationMs": 3.7399999999999998,
"taskDurationMs": 1200.376,
"heapDeltaBytes": -11575964,
"heapUsedBytes": 57046168,
"domNodes": -282,
"jsHeapTotalBytes": 20549632,
"scriptDurationMs": 164.73399999999998,
"eventListeners": -187,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1737.1550000000298,
"styleRecalcs": 31,
"styleRecalcDurationMs": 17.015,
"layouts": 6,
"layoutDurationMs": 0.6769999999999999,
"taskDurationMs": 384.311,
"heapDeltaBytes": -15815012,
"heapUsedBytes": 52985628,
"domNodes": 77,
"jsHeapTotalBytes": 21495808,
"scriptDurationMs": 28.053,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "canvas-zoom-sweep",
"durationMs": 1740.0179999999636,
"styleRecalcs": 32,
"styleRecalcDurationMs": 16.692999999999998,
"layouts": 6,
"layoutDurationMs": 0.5680000000000001,
"taskDurationMs": 399.244,
"heapDeltaBytes": -16075884,
"heapUsedBytes": 52520208,
"domNodes": 78,
"jsHeapTotalBytes": 21233664,
"scriptDurationMs": 31.170999999999996,
"eventListeners": 19,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 746.8910000000051,
"styleRecalcs": 10,
"styleRecalcDurationMs": 6.113,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 431.30500000000006,
"heapDeltaBytes": -2132128,
"heapUsedBytes": 66531544,
"domNodes": 16,
"jsHeapTotalBytes": 21495808,
"scriptDurationMs": 35.501999999999995,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "dom-widget-clipping",
"durationMs": 748.4090000000378,
"styleRecalcs": 10,
"styleRecalcDurationMs": 6.841999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 416.8419999999999,
"heapDeltaBytes": -2219348,
"heapUsedBytes": 66534456,
"domNodes": 16,
"jsHeapTotalBytes": 21757952,
"scriptDurationMs": 36.492999999999995,
"eventListeners": 2,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-idle",
"durationMs": 2063.1889999999657,
"styleRecalcs": 4,
"styleRecalcDurationMs": 3.2900000000000014,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 808.8629999999999,
"heapDeltaBytes": -4609700,
"heapUsedBytes": 61084060,
"domNodes": -284,
"jsHeapTotalBytes": 2195456,
"scriptDurationMs": 314.336,
"eventListeners": -183,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "large-graph-idle",
"durationMs": 2035.9849999999824,
"styleRecalcs": 5,
"styleRecalcDurationMs": 4.130000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 795.0500000000001,
"heapDeltaBytes": 4624324,
"heapUsedBytes": 69968152,
"domNodes": -283,
"jsHeapTotalBytes": 1933312,
"scriptDurationMs": 301.35900000000004,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 3644.4290000000024,
"styleRecalcs": 66,
"styleRecalcDurationMs": 10.986,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 2909.226,
"heapDeltaBytes": -3343520,
"heapUsedBytes": 60802948,
"domNodes": -283,
"jsHeapTotalBytes": 3244032,
"scriptDurationMs": 1368.6560000000002,
"eventListeners": -151,
"totalBlockingTimeMs": 1,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-pan",
"durationMs": 3686.2539999999626,
"styleRecalcs": 68,
"styleRecalcDurationMs": 11.601000000000003,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 2889.808,
"heapDeltaBytes": 4335964,
"heapUsedBytes": 68894448,
"domNodes": -283,
"jsHeapTotalBytes": 1863680,
"scriptDurationMs": 1346.1450000000002,
"eventListeners": -151,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "large-graph-zoom",
"durationMs": 5031.67400000001,
"styleRecalcs": 67,
"styleRecalcDurationMs": 11.724000000000002,
"layouts": 60,
"layoutDurationMs": 6.995000000000001,
"taskDurationMs": 3046.2949999999996,
"heapDeltaBytes": -14527932,
"heapUsedBytes": 58424556,
"domNodes": -267,
"jsHeapTotalBytes": 4030464,
"scriptDurationMs": 1435.237,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "large-graph-zoom",
"durationMs": 4965.813000000026,
"styleRecalcs": 68,
"styleRecalcDurationMs": 13.024999999999999,
"layouts": 60,
"layoutDurationMs": 7.486,
"taskDurationMs": 2966.66,
"heapDeltaBytes": -3682460,
"heapUsedBytes": 68133832,
"domNodes": 28,
"jsHeapTotalBytes": 6815744,
"scriptDurationMs": 1416.3809999999999,
"eventListeners": 12,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "legacy-node-drag",
"durationMs": 4164.250999999978,
"styleRecalcs": 42,
"styleRecalcDurationMs": 4.860999999999997,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 2821.8379999999997,
"heapDeltaBytes": -12682148,
"heapUsedBytes": 58573976,
"domNodes": -288,
"jsHeapTotalBytes": 9273344,
"scriptDurationMs": 751.795,
"eventListeners": -110,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "legacy-node-drag",
"durationMs": 4187.378000000081,
"styleRecalcs": 41,
"styleRecalcDurationMs": 4.067999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 2791.857,
"heapDeltaBytes": -12100720,
"heapUsedBytes": 59566036,
"domNodes": -290,
"jsHeapTotalBytes": 8224768,
"scriptDurationMs": 757.1999999999999,
"eventListeners": -108,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "minimap-idle",
"durationMs": 2026.4339999999947,
"styleRecalcs": 4,
"styleRecalcDurationMs": 3.4470000000000005,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 792.3769999999998,
"heapDeltaBytes": 2252608,
"heapUsedBytes": 67215316,
"domNodes": -285,
"jsHeapTotalBytes": 2719744,
"scriptDurationMs": 298.139,
"eventListeners": -183,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66999999999998,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "minimap-idle",
"durationMs": 2010.6129999999212,
"styleRecalcs": 3,
"styleRecalcDurationMs": 2.7600000000000002,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 782.525,
"heapDeltaBytes": -178392,
"heapUsedBytes": 66279864,
"domNodes": -286,
"jsHeapTotalBytes": 2457600,
"scriptDurationMs": 293.195,
"eventListeners": -185,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 779.8480000000154,
"styleRecalcs": 47,
"styleRecalcDurationMs": 10.264,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 562.994,
"heapDeltaBytes": -4958784,
"heapUsedBytes": 64319164,
"domNodes": 20,
"jsHeapTotalBytes": 22282240,
"scriptDurationMs": 154.07800000000003,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-dom-widget-clipping",
"durationMs": 696.8339999999671,
"styleRecalcs": 46,
"styleRecalcDurationMs": 10.194999999999999,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 510.66900000000004,
"heapDeltaBytes": -5021748,
"heapUsedBytes": 64467392,
"domNodes": 18,
"jsHeapTotalBytes": 22020096,
"scriptDurationMs": 133.483,
"eventListeners": 8,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "subgraph-idle",
"durationMs": 2005.5550000000153,
"styleRecalcs": 9,
"styleRecalcDurationMs": 7.388000000000003,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 457.9040000000001,
"heapDeltaBytes": 6701984,
"heapUsedBytes": 76072412,
"domNodes": 18,
"jsHeapTotalBytes": 20447232,
"scriptDurationMs": 18.345,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.699999999999818
},
{
"name": "subgraph-idle",
"durationMs": 2021.6649999999845,
"styleRecalcs": 10,
"styleRecalcDurationMs": 9.924,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 458.186,
"heapDeltaBytes": 6897332,
"heapUsedBytes": 76101704,
"domNodes": 20,
"jsHeapTotalBytes": 20447232,
"scriptDurationMs": 18.733,
"eventListeners": 4,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1855.634000000009,
"styleRecalcs": 77,
"styleRecalcDurationMs": 35.296,
"layouts": 16,
"layoutDurationMs": 4.215,
"taskDurationMs": 1086.882,
"heapDeltaBytes": -12751976,
"heapUsedBytes": 56750012,
"domNodes": -296,
"jsHeapTotalBytes": 19632128,
"scriptDurationMs": 116.748,
"eventListeners": -203,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "subgraph-mouse-sweep",
"durationMs": 1843.6619999999948,
"styleRecalcs": 77,
"styleRecalcDurationMs": 35.611999999999995,
"layouts": 16,
"layoutDurationMs": 3.9879999999999995,
"taskDurationMs": 1094.5,
"heapDeltaBytes": -12853740,
"heapUsedBytes": 56323060,
"domNodes": -296,
"jsHeapTotalBytes": 19632128,
"scriptDurationMs": 117.76,
"eventListeners": -203,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.800000000000182
},
{
"name": "subgraph-transition-enter",
"durationMs": 1367.4099999999498,
"styleRecalcs": 19,
"styleRecalcDurationMs": 30.128000000000004,
"layouts": 15,
"layoutDurationMs": 11.996,
"taskDurationMs": 912.9299999999998,
"heapDeltaBytes": -1487476,
"heapUsedBytes": 88042072,
"domNodes": 13671,
"jsHeapTotalBytes": 10223616,
"scriptDurationMs": 46.95999999999999,
"eventListeners": 2383,
"totalBlockingTimeMs": 130,
"frameDurationMs": 16.66333333333332,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 13578.40600000003,
"styleRecalcs": 247,
"styleRecalcDurationMs": 34.07,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 10116.137999999999,
"heapDeltaBytes": 8779040,
"heapUsedBytes": 72200640,
"domNodes": -280,
"jsHeapTotalBytes": 4747264,
"scriptDurationMs": 4701.871,
"eventListeners": -155,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "viewport-pan-sweep",
"durationMs": 13758.201999999983,
"styleRecalcs": 247,
"styleRecalcDurationMs": 33.592,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 10154.128999999999,
"heapDeltaBytes": -36212,
"heapUsedBytes": 63938744,
"domNodes": -279,
"jsHeapTotalBytes": 4030464,
"scriptDurationMs": 4720.203,
"eventListeners": -153,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "vue-large-graph-idle",
"durationMs": 17870.762000000013,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17849.296000000002,
"heapDeltaBytes": -26813556,
"heapUsedBytes": 166251176,
"domNodes": -6548,
"jsHeapTotalBytes": 5300224,
"scriptDurationMs": 1213.5900000000001,
"eventListeners": -7471,
"totalBlockingTimeMs": 0,
"frameDurationMs": 17.776666666666642,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-idle",
"durationMs": 17436.444999999934,
"styleRecalcs": 0,
"styleRecalcDurationMs": 0,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 17417.88,
"heapDeltaBytes": -24963356,
"heapUsedBytes": 165070244,
"domNodes": -6577,
"jsHeapTotalBytes": 2678784,
"scriptDurationMs": 1226.99,
"eventListeners": -7467,
"totalBlockingTimeMs": 0,
"frameDurationMs": 18.333333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 21677.874000000033,
"styleRecalcs": 150,
"styleRecalcDurationMs": 18.915000000000013,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 21659.370000000003,
"heapDeltaBytes": -50648512,
"heapUsedBytes": 156075420,
"domNodes": -6548,
"jsHeapTotalBytes": 5824512,
"scriptDurationMs": 1767.355,
"eventListeners": -7465,
"totalBlockingTimeMs": 484,
"frameDurationMs": 18.333333333333332,
"p95FrameDurationMs": 16.799999999999272
},
{
"name": "vue-large-graph-pan",
"durationMs": 21705.441000000064,
"styleRecalcs": 155,
"styleRecalcDurationMs": 18.785000000000053,
"layouts": 0,
"layoutDurationMs": 0,
"taskDurationMs": 21686.701,
"heapDeltaBytes": -35138512,
"heapUsedBytes": 169857784,
"domNodes": -6548,
"jsHeapTotalBytes": 3465216,
"scriptDurationMs": 1743.562,
"eventListeners": -7465,
"totalBlockingTimeMs": 335,
"frameDurationMs": 18.330000000000048,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 469.05500000002576,
"styleRecalcs": 17,
"styleRecalcDurationMs": 21.079,
"layouts": 3,
"layoutDurationMs": 0.694,
"taskDurationMs": 141.25799999999998,
"heapDeltaBytes": -14026256,
"heapUsedBytes": 52303404,
"domNodes": 126,
"jsHeapTotalBytes": 10223616,
"scriptDurationMs": 10.697,
"eventListeners": 97,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.666666666666668,
"p95FrameDurationMs": 16.700000000000728
},
{
"name": "workflow-execution",
"durationMs": 463.40199999997367,
"styleRecalcs": 15,
"styleRecalcDurationMs": 23.494999999999997,
"layouts": 3,
"layoutDurationMs": 0.67,
"taskDurationMs": 131.774,
"heapDeltaBytes": -14118272,
"heapUsedBytes": 52387840,
"domNodes": 123,
"jsHeapTotalBytes": 9699328,
"scriptDurationMs": 10.123,
"eventListeners": 97,
"totalBlockingTimeMs": 0,
"frameDurationMs": 16.66333333333335,
"p95FrameDurationMs": 16.799999999999272
}
]
} |
Codecov Report❌ Patch coverage is @@ Coverage Diff @@
## drjkl/split-layout-attachment #14657 +/- ##
================================================================
Coverage ? 81.09%
================================================================
Files ? 1898
Lines ? 109884
Branches ? 34676
================================================================
Hits ? 89110
Misses ? 20383
Partials ? 391
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
ef7dae4 to
0c4bb12
Compare
4c47c5e to
acfe008
Compare
0c4bb12 to
65ffdc3
Compare
acfe008 to
51aba3c
Compare
65ffdc3 to
b7ca00a
Compare
c852026 to
8f44f95
Compare
c95b8eb to
65f0318
Compare
8f44f95 to
a639061
Compare
329456c to
b481407
Compare
a639061 to
5ed6da7
Compare
🔧 Auto-fixes AppliedThis PR has been automatically updated to fix linting and formatting issues.
Changes made:
|
b481407 to
086008e
Compare
9c0a0b1 to
c66c1ba
Compare
c66c1ba to
b8d374d
Compare
|
Heads up on The rewrite in this PR ( I checked it rather than inferred it — porting the regression test to your stack tip reproduces it identically to Two things worth knowing if you fix it inline:
I put the port up as #15093, based on this branch so the diff is one commit. Fold it into your rewrite and close mine if that is cheaper — I mainly wanted it tracked somewhere it cannot get lost between the stack and |
b3850f1 to
9f63eac
Compare
b8d374d to
9c1b0ba
Compare
9f63eac to
827386f
Compare
9c1b0ba to
201a2a7
Compare
827386f to
66f9af2
Compare
Retargeted squash of the layout-source stack onto the pruned attachment rework: useLayoutMutations(source) bakes the source when instantiated, operations carry their source explicitly, the store stamps the session actor on operations that do not carry one, and the ambient setSource/setActor/getCurrentSource/getCurrentActor knobs are removed. DOM height convention is passed explicitly through batchUpdateNodeBounds options instead of sniffed from an ambient DOM source.
66f9af2 to
a43634e
Compare
201a2a7 to
458b00c
Compare
|
Superseded by #15017, which includes the explicit source attribution and DOM height convention fix along with the finalized attachment architecture. No unique changes remain in this PR. |
Stacked on #14480. Six commits: one bug fix, five removals. +355/−1113.
The layout store carried a process-global
currentSource, written by one subsystem and read by another for a purpose neither named. This PR fixes the bug that came out of it, then deletes the mechanism and everything that turned out to be holding onto it.The bug
batchUpdateNodeBoundsread the ambientLayoutSourceas an out-of-band flag meaning "these bounds were measured off the DOM and include the title height", overwrote the source toVuefor the operation it emitted, then restored the original on exit. The only producer ofLayoutSource.DOMwas the sharedResizeObserver, which set it and never cleared it.ResizeObserversetsDOMand leaves it set.startDragsetsVueonce, at pointerdown.endDragcallsbatchUpdateNodeBoundsat pointerup with nothing setting a source in between.shouldNormalizeHeightsis true, and the title height comes off a second time.Snap-on-drop shrinks the snapped nodes by
NODE_TITLE_HEIGHT. The window is narrower than "any observer during any drag" — aCanvas-sourced write clearsDOM— so it needs the observer to fire with nothing else attributing after it. Invisible at every call site individually; it exists only in the gap between them.The regression test was written against the old code first and fails on it:
expected 170 to be 200.Changes
batchUpdateNodeBounds(rootGraphId, updates, { source, boundsIncludeTitleHeight })— the measurement convention travels with the call. Splits the two jobs ambient source was doing: who made the change, and what convention the caller measured in.useLayoutMutations(source)— required, not optional. An optional parameter reintroduces the ambient default through another door. An audit of all thirteensetSourcecall sites found zero that varied source per call: eleven fixed for the caller's lifetime, one fake-varying, one not attribution at all.currentSource,setSource,getCurrentSource,getCurrentActor,setActor,ACTOR_CONFIG.DEFAULT_SOURCE. Operations may omitactor; the store stamps its own session actor, so no caller needs to know it.LayoutSourceisCanvas | Vue.DOMdied with the explicit flag.Externalwas never emitted by anything — it existed only as the store's initial value, which madeLGraphNode.vue's guard branch reachable only before the firstsetSourcein the process.clearGraphdeleted — zero production callers.LGraph.clearsweeps layout per-entity throughunregisterAllGraphLayout; the prefix-wipe had been dead since. TakesClearGraphOperationand its dispatch case with it.customRef's write path deleted — zero production writers across twenty consumers, all of which only read. It was building four operations by hand from a diff of old and new layout.getNodeLayoutRefreturns aComputedRefnow.useLayoutSyncdeleted — orphaned whenGraphCanvas.vuemoved tonotifyLayoutChanges, and kept off knip's radar by its own test file. 354 lines.useNodeZIndex'slayoutSourceoption and per-callsourceoverride deleted — no production caller passed either, and the tests covering them asserted the mock.Review focus
getNodeLayoutRefchanges primitive,customRef→computed.customRef's contract requires aset, so making the ref read-only meant changing it. This is the one part of the removal that is not semantically inert: reads are now cached between store notifications, where the old ref re-read Yjs on every access. Safe becauseynodes.observeDeepfires the node's trigger for every mutation path including remoteapplyUpdate— the same trigger the old ref used — but it deserves a look.actoris now optional onLayoutOperation. No production caller passes one. The only submitters are fix(layout): enforce per-instance geometry ownership #14480's tests asserting the operation's actor becomes the Yjs transaction origin, so the store fills it with??=rather than overwriting, which preserves that contract.canvasLayoutMutations()became a module const. It read like an accessor and mutated a singleton. Import-time evaluation is inert —useLayoutMutationsonly builds closures.docs/adr/0003anddocs/architecture/node-data-store.mdboth described a root-graph teardown callinglayoutStore.clearGraph(graphId). That was already false before this PR.Emitted source is deliberately unchanged
batchUpdateNodeBoundsstill stamps every operationVueregardless of caller, so paste and arrange keep reportingVueeven though they meanCanvas. Correcting that turns onLGraphNode.vue's CSS-var rewrite for those paths — a visible behaviour change that wants its own review, so it is not in this PR. It got considerably cheaper here: the microtask-vs-rAF scheduling half of that risk lived inuseLayoutSync, which this PR deletes.